package br.odb.portalizer;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;

import br.odb.libscene.Mesh;
import br.odb.libscene.Sector;
import br.odb.libscene.Sector.InvalidSlotException;
import br.odb.libscene.World;
import br.odb.utils.Utils;

/**
 * 
 * @author Daniel "Monty" Monteiro
 * 
 */
public class WorldPartitioner {

	static boolean multipass = false;
	static boolean hollow = true;
	static boolean generatePVS = true;
	public static int transientStartSector;

	public static void secureLog(final String msg) {
		try {
			System.out.println(msg);
		} catch (final Throwable t) {

		}
	}

	/**
	 * 
	 */
	public static World optimize(final World world) {
		return optimizeLocalPartitioning(world, false);
	}

	public static Sector split(final Sector sector, final Hyperplane hyperplane) {

		Sector toReturn;
		toReturn = null;

		if (hyperplane.getVector().getX() != Integer.MAX_VALUE) {
			// plane in YZ

			if (sector.getX0() < hyperplane.getVector().getX()
					&& hyperplane.getVector().getX() < sector.getX1()) {
				toReturn = new Sector(sector);
				toReturn.setX0(snap(hyperplane.getVector().getX()));
				sector.setX1(snap(hyperplane.getVector().getX()));
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);
			}

		} else if (hyperplane.getVector().getY() != Integer.MAX_VALUE) {
			// plane in XZ
			if (sector.getY0() < hyperplane.getVector().getY()
					&& hyperplane.getVector().getY() < sector.getY1()) {
				toReturn = new Sector(sector);
				toReturn.setY0(snap(hyperplane.getVector().getY()));
				sector.setY1(snap(hyperplane.getVector().getY()));
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);
			}

		} else if (hyperplane.getVector().getZ() != Integer.MAX_VALUE) {
			// plane in XY
			if (sector.getZ0() < hyperplane.getVector().getZ()
					&& hyperplane.getVector().getZ() < sector.getZ1()) {
				toReturn = new Sector(sector);
				toReturn.setZ0(snap(hyperplane.getVector().getZ()));
				sector.setZ1(snap(hyperplane.getVector().getZ()));
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);

			}

		}

		
		
		return toReturn;
	}

	private static float snap(float val) {

		// if ( Mesh.snapBoundsToVertexes )
		// return Math.round( val );
		// else
		return val;
	}

	public static Hyperplane generateHyperplane(final Sector sector,
			final byte kind) {
		float n = 0.0f;

		switch (kind) {
		case Sector.FACE_N:
			n = sector.getZ0();
			break;
		case Sector.FACE_S:
			n = sector.getZ1();
			break;
		case Sector.FACE_W:
			n = sector.getX0();
			break;
		case Sector.FACE_E:
			n = sector.getX1();
			break;
		case Sector.FACE_FLOOR:
			n = sector.getY0();
			break;
		case Sector.FACE_CEILING:
			n = sector.getY1();
			break;
		}

		return new Hyperplane(kind, n, sector);
	}

	public static World optimizeLocalPartitioning(final World world, final boolean preserveIds) {
		Sector sector;
		Sector current;
		Sector splitting;
		Sector generatee;
		Hyperplane hyperplane;
		Vector<Sector> workArea = new Vector<Sector>();
		World toReturn = new World();
		int originalId;
		long startTime;
		final Vector<Hyperplane> planes = new Vector<Hyperplane>();
		final Vector<Sector> generated = new Vector<Sector>();
		final Vector<Sector> toRemove = new Vector<Sector>();
		sector = null;
		boolean changed = true;
		int pass = 0;
		
		for (int c = 1; c < world.getTotalSectors(); ++c) {

			current = world.getSector(c);

			if (current != null && !current.isDegenerate()) {

				originalId = current.getId();

				workArea.add(current);

//				if (preserveIds)
//					current.setId(originalId);
				
				current.setId( c );
				current.setParent( c );
			} else if (current != null && current.isDegenerate()) {
				secureLog("setor " + c + " is degenerated");
			}
		}
		
		
		while ( changed ) {
			changed = false;
			for ( int f = 0; f < workArea.size(); ++f ) {
				
				current = workArea.get(f);
				current.removeAllDoors();
				//current.setParent( f + 1 );

				for (int e = 0; e < workArea.size(); ++e ) {
					
					planes.clear();
					
					splitting = workArea.get( e );
					
					if ( current == splitting )
						continue;
					
					int n = current.touches( splitting ) ? current.touchesInPoints( splitting ) : 0;
					
					//if ( n == 4 || n == 0 )
					if ( !current.touches( splitting ) )
						continue;
					
					System.out.println("pass " + pass + " f:" + f+ " e:" + e + " of " + workArea.size() + " = " + ( ( ( float ) f ) / workArea.size() ) );
					
					generated.clear();
					
					generated.add( splitting );
					
					for (byte d = 0; d < 6; ++d) {
						
						hyperplane = generateHyperplane(current, d);
						
						for ( int c = 0; c < generated.size(); ++c ) {
							
							generatee = generated.get( c );
							sector = split( generatee, hyperplane);
							
							if (sector != null && !sector.isDegenerate()) {
								sector.setParent( splitting.getParent() );
								System.out.println("split");
								generated.add( sector );
							}
						}
					}
					
					for ( int c = 0; c < generated.size(); ++c ) {
					//	toReturn.addSector( generated.get( c ) );
						if ( !workArea.contains( generated.get( c ) ) )
							workArea.add( generated.get( c ) );
					//	changed = true;
					}
				}
			}
			
			++pass;
			
			System.out.println( "end of pass " + pass );
		}
		
		changed = true;
		check_pass: while (changed) {
			secureLog("checando...");
			changed = false;
			toRemove.clear();

			for (int z = 0; z < workArea.size(); ++z) {

				if (toRemove.contains(workArea.get(z)))
					continue;

				/*
				 * if ( world.getSector( z ).getDX() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em x: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDY() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em y: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDZ() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em z: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 */
				for (int w = z + 1; w < workArea.size(); ++w) {

					if (workArea.get(z).coincidant(workArea
							.get(w))) {

						if (!toRemove.contains(workArea.get(w)))
							toRemove.add(workArea.get(w));

						// secureLog("setores juntos: " + z + " e " + w);

						if (multipass)
							changed = true;

					} else {
						if (workArea.get(z).contains(workArea
								.get(w))) {
							// secureLog("setores se incluem: " + z + " e " +
							// w);

							if (multipass)
								changed = true;
						}
					}

				}

				if (z < workArea.size() && workArea.get(z).isSolid()) {
					secureLog("setor solido encontrado!");

					if (multipass)
						changed = true;

					if (!toRemove.contains(workArea.get(z)))
						toRemove.add(workArea.get(z));

					continue;
				}
			}
			secureLog("acabei?");

			if (toRemove.size() > 0)
				changed = true;

			for (int c = 0; c < toRemove.size(); ++c) {
				// secureLog("removing " + toRemove.get(c).getId() + "...");
				workArea.remove(toRemove.get(c));
			}
		}

		secureLog(" finished!");
		secureLog(" generated " + workArea.size() + " sector(s)");
		
		
		
		for (int c = 0; c < workArea.size(); ++c) {
			
			current = workArea.get(c);
			current.setId(c + 1);
			current.setIsMaster( false );

			if (WorldPartitioner.transientStartSector != -1) {

				secureLog("currentParent " + current.getParent() + " / "
						+ WorldPartitioner.transientStartSector);

				if (current.getParent() == WorldPartitioner.transientStartSector) {

					WorldPartitioner.transientStartSector = current.getId();
					secureLog("starting at sector " + current.getId());
				}
			}

			toReturn.addSector(current);
		}
		
		//generatePVS( toReturn );
		
		return toReturn;
	}
	
	
	public static void generatePVS( World world ) {
	
		Sector s0;
		Sector s1;
		//HashMap< Sector, Sector > canSeeEachOther = new HashMap< Sector, Sector >();
		
		for ( int c = 0; c < world.getTotalSectors(); ++c ) {
			s0 = world.getSector( c );
			
			if ( !s0.isMaster() )
				continue;
			
			for ( int d = 0; d < world.getTotalSectors(); ++d ) {
				
				if ( c == d )
					continue;
	
				s1 = world.getSector( d );
				
				if ( !s1.isMaster() )
					continue;
				
				//if ( canSeeEachOther.) 
				//continue;
				
//				if ( world.canSee( c, d, true ) ) {
////					canSeeEachOther.put( s0, s1 );
////					canSeeEachOther.put( s1, s0 );
//				}
			}
		}
	}
	
	
	/**
	 * 
	 */
	public static World optimizeGlobalPartitioning(final World world, final boolean preserveIds) {

		Vector<Sector> workArea = new Vector<Sector>();
		World toReturn = new World();
		Sector sector;
		Sector current;
		Sector splitting;
		Hyperplane hyperplane;
		boolean changed = true;
		int originalId;
		long startTime;
		final Vector<Hyperplane> planes = new Vector<Hyperplane>();
		final Vector<Sector> toRemove = new Vector<Sector>();
		sector = null;

		// não existe world sem master sector. é lei.

		for (int c = 1; c < world.getTotalSectors(); ++c) {

			current = world.getSector(c);

			if (current != null && !current.isDegenerate()) {

				originalId = current.getId();

				workArea.add(current);

				if (preserveIds)
					current.setId(originalId);

			} else if (current != null && current.isDegenerate()) {
				secureLog("setor " + c + " is degenerated");
			}

		}

		int passes = 0;
		split_pass: 
			while (changed) {
				changed = false;
				passes++;
				secureLog("pass: " + passes + " for " + workArea.size()
						+ " sector(s)");
	
				startTime = System.currentTimeMillis();
				planes.clear();
	
				for (int f = 0; f < workArea.size(); ++f) {
	
					current = workArea.get(f);
					current.removeAllDoors();
					current.setParent(current.getId());
					secureLog("parent " + current.getParent());
	
					if (current != null && current.isDegenerate()) {
						workArea.remove(current);
	
						if (multipass)
							changed = true;
	
						continue split_pass;
					}
	
					for (byte d = 0; d < 6; ++d) {
	
						hyperplane = generateHyperplane(current, d);
	
						if (!planes.contains(hyperplane))
							planes.add(hyperplane);
					}
				}
				System.out.println("gerar os hiperplanos levou "
						+ ((System.currentTimeMillis() - startTime))
						+ " milisegundos");
	
				for (int e = 0; e < workArea.size(); ++e) {
	
					splitting = workArea.get(e);
	
					for (int c = 0; c < planes.size(); ++c) {
	
						hyperplane = planes.get(c);
	
						if (hyperplane.getGenerator() == splitting)
							continue;
						
	//					if ( !hyperplane.getGenerator().contains( splitting ) || 
	//							!hyperplane.getGenerator().touches( splitting ) )
	//						continue;
	
						sector = split(splitting, hyperplane);
	
						if (sector != null && !sector.isDegenerate()) {
	
							if (multipass)
								changed = true;
	
							if (!workArea.contains(sector))
								workArea.add(sector);
	
							if (preserveIds)
								sector.setId(splitting.getId());
						}
					}
				}

			}	

		for (int z = 0; z < workArea.size(); ++z) {
			workArea.get(z).setId(z);
		}

		changed = true;
		check_pass: while (changed) {
			secureLog("checando...");
			changed = false;
			toRemove.clear();

			for (int z = 0; z < workArea.size(); ++z) {

				if (toRemove.contains(workArea.get(z)))
					continue;

				/*
				 * if ( world.getSector( z ).getDX() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em x: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDY() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em y: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDZ() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em z: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 */
				for (int w = z + 1; w < workArea.size(); ++w) {

					if (workArea.get(z).coincidant(workArea
							.get(w))) {

						if (!toRemove.contains(workArea.get(w)))
							toRemove.add(workArea.get(w));

						// secureLog("setores juntos: " + z + " e " + w);

						if (multipass)
							changed = true;

					} else {
						if (workArea.get(z).contains(workArea
								.get(w))) {
							// secureLog("setores se incluem: " + z + " e " +
							// w);

							if (multipass)
								changed = true;
						}
					}

				}

				if (z < workArea.size() && workArea.get(z).isSolid()) {
					secureLog("setor solido encontrado!");

					if (multipass)
						changed = true;

					if (!toRemove.contains(workArea.get(z)))
						toRemove.add(workArea.get(z));

					continue;
				}
			}
			secureLog("acabei?");

			if (toRemove.size() > 0)
				changed = true;

			for (int c = 0; c < toRemove.size(); ++c) {
				// secureLog("removing " + toRemove.get(c).getId() + "...");
				workArea.remove(toRemove.get(c));
			}
		}

		secureLog(" finished!");
		secureLog(" generated " + workArea.size() + " sector(s)");

		toReturn = new World();

		for (int c = 0; c < workArea.size(); ++c) {
			
			current = workArea.get(c);
			current.setId(c + 1);
			current.setIsMaster( false );

			if (WorldPartitioner.transientStartSector != -1) {

				secureLog("currentParent " + current.getParent() + " / "
						+ WorldPartitioner.transientStartSector);

				if (current.getParent() == WorldPartitioner.transientStartSector) {

					WorldPartitioner.transientStartSector = current.getId();
					secureLog("starting at sector " + current.getId());
				}
			}

			toReturn.addSector(current);
		}

		return toReturn;
	}

	public static void dumpTopViewToSVG(final World world, final String filename) {
		Sector sector;

		try {
			final FileOutputStream fos = new FileOutputStream(filename);
			final BufferedWriter bw = new BufferedWriter(
					new OutputStreamWriter(fos));

			bw.write("<?xml version='1.0' encoding='UTF-8' standalone='no'?>");
			bw.newLine();
			bw.write("<svg>");
			bw.newLine();
			bw.write("<g>");
			bw.newLine();

			for (int c = 0; c < world.getTotalSectors(); ++c) {

				sector = world.getSector(c);
				bw.write(sector.toSVGTopView());
				bw.newLine();
			}

			bw.write("</g>");
			bw.newLine();
			bw.write("</svg>");
			bw.newLine();
			bw.close();

		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	public static void buildConvexHulls(final World world, final ArrayList<Mesh> mesh2) {
		Mesh mesh;
		world.addSector(new Sector(0.0f, 255.0f, 0.0f, 255.0f, 0.0f, 255.0f));

		for (int c = 0; c < mesh2.size(); ++c) {
			mesh = (Mesh) mesh2.get(c);
			world.addSector(mesh.getConvexHull());
		}
	}

	public static void guessLinks(final World world, boolean guessDoors) {

		Sector s1;
		Sector s2;

		for (int c = 1; c < world.getTotalSectors(); ++c) {
			s1 = world.getSector(c);
			s1.setId(c);
		}

		for (int c = 1; c < world.getTotalSectors(); ++c) {

			s1 = world.getSector(c);
			
			if ( s1.isMaster() )
				continue;

			if (s1.isSolid())
				continue;

			for (int d = 1; d < world.getTotalSectors(); ++d) {

				if (c == d)
					continue;

				s2 = world.getSector(d);
				
				if ( s2.isMaster() )
					continue;

				if (s2.isSolid())
					continue;

				if (Utils.eqFloat(s1.getX0(), s2.getX1())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_W, s2.getId());
					s2.setLink(Sector.FACE_E, s1.getId());
				}

				if (Utils.eqFloat(s1.getX1(), s2.getX0())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_E, s2.getId());
					s2.setLink(Sector.FACE_W, s1.getId());
				}

				if (Utils.eqFloat(s1.getY0(), s2.getY1())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())) {
					s1.setLink(Sector.FACE_FLOOR, s2.getId());
					s2.setLink(Sector.FACE_CEILING, s1.getId());
				}

				if (Utils.eqFloat(s1.getY1(), s2.getY0())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())) {
					s1.setLink(Sector.FACE_CEILING, s2.getId());
					s2.setLink(Sector.FACE_FLOOR, s1.getId());
				}

				if (Utils.eqFloat(s1.getZ0(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
				 		&& Utils.eqFloat(s1.getX1(), s2.getX1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_N, s2.getId());
					s2.setLink(Sector.FACE_S, s1.getId());
				}

				if (Utils.eqFloat(s1.getZ1(), s2.getZ0())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_S, s2.getId());
					s2.setLink(Sector.FACE_N, s1.getId());
				}
			}
		}
	}

	public static void guessDoors( World world ) {

		Sector s1;
		Sector s2;
		Sector s1_master;
		Sector s2_master;
		
		for (int c = 0; c < world.getTotalSectors(); ++c) {
			
			s1 = world.getSector(c);
			
			if ( s1.isMaster() )
				continue;
			
			if ( s1.getParent() != 0 )
				s1_master = world.getSector( s1.getParent() );
			else
				continue;
			
			System.out.println( "setor " +c + " tem pai " + s1.getParent() );
			
				
			for (int d = 0; d < 6; ++d) {
				try {

					if (s1_master.getDoor(d) != null) {
						

						
						if (s1.getLink(d) != 0) {
							
							s2 = world.getSector(s1.getLink(d));

							if ( s2.getParent() != 0 && s1.getParent() != s2.getParent() )
								s2_master = world.getSector( s2.getParent() );
							else
								continue;
							
							System.out.println( "setor " + s1_master.getId() + " tem porta em " + d + " ( via " + c + " com " + s1.getLink(d) + ", filho de " + s2_master.getId() + " )" );
							
							s1.setDoorAt(d, s1.getLink( d ) );
							s2.setDoorAt(Utils.getOppositeDirection( d ), s2.getLink( Utils.getOppositeDirection( d ) ) );
						}

					}
				} catch (final InvalidSlotException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void renewIds(final World world,
			final boolean preserveNegatives) {
		for (int c = 0; c < world.getTotalSectors(); ++c) {
			if (!preserveNegatives || world.getSector(c).getId() >= 0) {
				world.getSector(c).setId(c);
			}
		}
	}

	public static void removeNegatives(final World world) {
		for (int c = 0; c < world.getTotalSectors(); ++c) {
			if (world.getSector(c).getId() < 0) {
				world.removeSector(c, false);
				c -= 2;
			}
		}
	}

	public static void deleteHollowSectors(World world) {
		Vector<Sector> toDelete = new Vector<Sector>();

		for (int c = 1; c < world.getTotalSectors(); ++c) {
			if (world.getSector(c).getVisibleFacesCount() == 0) {
				toDelete.add(world.getSector(c));
			}
		}

		for (int c = 0; c < toDelete.size(); ++c)
			world.removeSector(toDelete.get(c));
	}
}
